Skip to content

Add the space-discarding feature - #182

Open
isuffix wants to merge 1 commit into
typst:mainfrom
isuffix:space-discarding
Open

Add the space-discarding feature#182
isuffix wants to merge 1 commit into
typst:mainfrom
isuffix:space-discarding

Conversation

@isuffix

@isuffix isuffix commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

This adds the space-discarding feature as described at typst/typst#7350 (comment), although I have changed from "whether a writing system uses spaces between words" to "whether a writing system uses spaces at all."

I will leave this PR description short as the code itself contains a plenty of discussion of rationale and implementation considerations, along with my research into the usage of space characters in various writing systems.

There is a lot of writing here, so I would really appreciate help with checking for typos and inconsistencies, as it has become hard for me to consider everything with fresh eyes. I am very amenable to suggestions :)

I will also restate that I only speak English and while I have tried to do good research, I am not infallible. I would appreciate any input from native speakers of Chinese or Japanese or any of the other writing systems discussed in the PR.

I would also like to thank @r12a for his wonderfully detailed orthography descriptions and script comparison table, without which this PR would not be nearly as complete or authoritative. If you're reading this, I would love any feedback you could provide.

@r12a

r12a commented Aug 13, 2026

Copy link
Copy Markdown

Tibetan comes to mind as an example of an orthography that doesn't use (ASCII) spaces but does have delimiters (syllable-based) which could break a line in the source code, but which should not incur an extra space when stitching things together (see Tibetan Orthography Notes). hth

@YDX-2147483647 YDX-2147483647 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone through the source code (except the emoji part). I believe the current algorithm is simple and robust enough. I only have some suggestions regarding doc comments and tests. See my individual comments.

More materials supporting the current algorithm

East Asian Width

UAX #14: Unicode Line Breaking Algorithm also uses East Asian Width to filter out East Asian characters.

The symbol $EastAsian stands for the set [\p{ea=F}\p{ea=W}\p{ea=H}] of characters with Fullwidth, Wide, or Halfwidth East Asian Width.

Pandoc

Pandoc's east_asian_line_breaks extension uses charWidth to determine if a soft break (e.g., single newline in markdown and typst) should be removed. However, we've argued in typst/typst#7350 (comment) that the rules for determining widths are too complicated and it's better to use East Asian Width directly.

LaTeX

LaTeX cannot be taken as a reference, because the implementations are limited by the technology. Specifically, the whitespace in 字\n“ should be discarded, but luatexja keeps it. And the whitespace in ”\nA should be kept as a word space, but xeCJK discards it. See typst/typst#792 (comment) for the tests.

Typst cjk-unbreak

As for typst packages, cjk-unbreak uses the following regex to determine if a character is CJ (Chinese + Japanese) and discards the space iff either side matches the regex.

[\p{Han},。;:!?‘’“”()「」【】…—\p{Hiragana}\p{Katakana}]

This package is designed only for CJ, so the regex includes a few characters that are considered YesOrAmbiguous in this PR.
The algorithm in this PR is designed for all writing systems, so the difference to cjk-unbreak is acceptable.

Typst cjk-spacer

A newer typst package, cjk-spacer, uses a more complex algorithm. If I understand correctly, then its algorithm is equivalent to the following.

#let default-cjk-regex = regex(
  "["
    + "\p{scx:Hira}\p{scx:Kana}\p{scx:Han}\p{scx:Hang}\p{scx:Bopo}"
    + "\u3000-\u303F" // CJK Symbols and Punctuation
    + "\u3190-\u319F" // Kanbun
    + "\u31C0-\u31EF" // CJK Strokes
    + "\u3200-\u32FF" // Enclosed CJK Letters and Months
    + "\u3300-\u33FF" // CJK Compatibility
    + "\uFE10-\uFE1F" // Vertical Forms
    + "\uFE30-\uFE4F" // CJK Compatibility Forms
    + "\uFE50-\uFE6F" // Small Form Variants
    + "\uFF00-\uFFEF" // Halfwidth and Fullwidth Forms
    + "]",
)
#let default-western-open-punc-regex = regex(
  "[\p{Pi}\p{Ps}--["
    + "\u3000-\u303F" // CJK Symbols and Punctuation
    + "\uFE10-\uFE1F" // Vertical Forms
    + "\uFE30-\uFE4F" // CJK Compatibility Forms
    + "\uFE50-\uFE6F" // Small Form Variants
    + "\uFF00-\uFFEF" // Halfwidth and Fullwidth Forms
    + "]]",
)
#let default-western-close-punc-regex = regex(
  "[\p{Pf}\p{Pe}\p{Term}--["
    + "\u3000-\u303F" // CJK Symbols and Punctuation
    + "\uFE10-\uFE1F" // Vertical Forms
    + "\uFE30-\uFE4F" // CJK Compatibility Forms
    + "\uFE50-\uFE6F" // Small Form Variants
    + "\uFF00-\uFFEF" // Halfwidth and Fullwidth Forms
    + "]]",
)

#let discard_space_between(before, after) = {
  if after.matches(western-open-punc-regex).len() == 0 and after.starts-with(cjk-regex) {
    true
  } else if before.matches(western-close-punc-regex).len() == 0 and before.ends-with(cjk-regex) {
    true
  } else {
    false
  }
}

The cjk-spacer algorithm does not merely consider the characters immediately adjacent to the space, but rather the text segments around the space. This approach is appropriate when typesetting a document, but it's too surprising at the syntax level.

Also, cjk-spacer treats K the same as CJ. According to previous feedbacks in typst/typst#7350, discarding spaces is not preferable for Korean texts.

And I haven't check if the Unicode blocks enumerated by cjk-spacer are equivalent to this PR, but I think it's worth checking before merging this PR.

Comment thread src/space_discarding.rs
/// kept.
///
/// Currently this check includes characters which we determine to be from the
/// Chinese, Japanese, or Yi writing systems plus ideographic punctuation. Note

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The term ideographic punctuation needs clarification.

It looks like that you use this term colloquially. I suggest putting the relevant test cases in a separate function and linking to it. (similar to test_spacing_emoji_presentation)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll split into two test functions and integrate the "Miscellaneous" section into the two punctuation groups.

Do you think we should consider special-casing or ?

Comment thread src/space_discarding.rs Outdated
Comment thread src/space_discarding.rs Outdated
Comment thread src/space_discarding.rs Outdated
Comment thread src/space_discarding.rs Outdated
check_spacing('₩', YesOrAmbiguous);
check_spacing('₩', No);
check_spacing('¥', YesOrAmbiguous);
check_spacing('¥', No);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment for your information: I get the narrow ¥ when I type in Chinese mode on my mobile phone, but I get the fullwidth ¥ when I press Shift+4 ($) in Chinese mode on my desktop computer.

Comment thread src/space_discarding.rs Outdated
@@ -0,0 +1,462 @@
//! Whether to keep or discard spaces that are inferred due to newlines in

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's necessary to explain the meaning of discarding spaces here, as the word space is also used to refer to the lack of ink below in WritingSystemSpacing.

My suggestion:

  • Refer to the lack of ink as spacing.
  • Refer to U+0020 SPACE and \n as whitespace characters.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll change all of the introductory sentences for doc comments to make sure they use space characters, but I don't I don't really want to read whitespace characters everywhere it currently says spaces. I think as long as the introductions are consistent the usage should be clear.

Comment thread src/space_discarding.rs Outdated
Comment thread src/space_discarding.rs

@laurmaedje laurmaedje left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic-wise, this seems reasonable and very well supported in terms of rationale. I'd trust in the research and the other involved people's judgement. I just have some minor nits.

Comment thread src/space_discarding.rs Outdated
Comment thread src/space_discarding.rs Outdated
Comment thread src/space_discarding.rs Outdated
Comment thread Cargo.toml Outdated
Comment thread src/space_discarding.rs Outdated
@laurmaedje laurmaedje added the waiting on author This is waiting on an action by the author label Aug 18, 2026
@isuffix

isuffix commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

I've updated comments and tests based on all of the feedback!

I think for this initial PR we will not include Tibetan. But one of the main points for moving this to Codex is so we can add GitHub issues and iterate at a separate pace than typst/typst. So we should add an issue for Tibetan support and discuss it more once this is merged.

For cjk-unbreak, it looks there are quite a few differences to this PR. The characters it doesn't include are all either Yi only or are ScriptExtensions=Common that have EastAsianWidth=Wide. For the other way around, this UnicodeSet link grouped by EastAsianWidth with Script=Hangul removed, shows 89 characters.

Of those, it seems the groups we should consider are:

  1. The fullwidth latin letters: U+FF21-U+FF5A (A - z)
  2. The circled numbers on black squares: U+3248-U+324F (㉈-㉏)
  3. The six CJ marks with Sc=Inherited and EA=W: U+302A-U+302D and U+3099-U+309A
  4. The corner tone marks: U+A700-U+A707 (Wikipedia)
  5. U+303F IDEOGRAPHIC HALF FILL SPACE

I'll prepare an update to include these.

UnicodeSet syntax for this PR and cjk-unbreak

This PR currently:

[
  \p{Han} \p{Kana} \p{Hira} \p{Bopo} \p{Yi}
  [ \p{Common} & [\p{EA=H}\p{EA=F}\p{EA=W}] - \p{Emoji} - [] ]
]

cjk-unbreak default (excluding \p{Scx=Hang}):

[
  \p{Scx=Hira} \p{Scx=Kana} \p{Scx=Han} \p{Scx=Bopo}
  [ \u3000-\u303F \u3190-\u319F \u31C0-\u31EF \u3200-\u32FF \u3300-\u33FF \uFE10-\uFE1F \uFE30-\uFE4F \uFE50-\uFE6F \uFF00-\uFFEF ]
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting on author This is waiting on an action by the author

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants